package com.sen.common.security;

import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;

import com.sen.common.utils.web.ServletUtils;

/**
 * 一个简单的图片验证码生成/校验器.
 * 
 * 在web.xml中配置的参数包括：
 * <ol>
 * <li>captchaRange -- 验证码的取值范围,
 * 默认为"ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789".
 * <li>imageWidth -- 图片的宽度, 默认为"57".
 * <li>imageHeight -- 图片的高度, 默认为"25".
 * <li>captchaCount -- 验证码的个数, 默认为"4".
 * <li>sessionKey -- session中存放验证码的KEY, 默认为"sessionKey".
 * <li>interferenceCount -- 干扰的连线数, 默认为"10".
 * </ol>
 * 
 * @author sen
 */
public class SimpleImageCaptchaServlet extends HttpServlet {

	private static final long serialVersionUID = -2367796992805970047L;

	// web.xml中的参数名定义
	/**
	 * 验证码的取值范围.
	 */
	private static final String PARAM_CAPTCHA_RANGE = "captchaRange";
	/**
	 * 图片宽度.
	 */
	private static final String PARAM_IMAGE_WIDTH = "imageWidth";
	/**
	 * 图片高度.
	 */
	private static final String PARAM_IMAGE_HEIGHT = "imageHeight";
	/**
	 * 验证码个数.
	 */
	private static final String PARAM_CAPTCHA_COUNT = "captchaCount";
	/**
	 * session中存放验证码的KEY.
	 */
	private static final String PARAM_SESSION_KEY = "sessionKey";
	/**
	 * 背景的颜色范围. 格式为: nnn,nnn 的字符串.
	 */
	private static final String PARAM_BACKGROUND_COLOR_RANGE = "backgroundColorRange";
	/**
	 * 干扰连线的颜色范围. 格式为: nnn,nnn 的字符串.
	 */
	private static final String PARAM_INTERFERENCE_COLOR_RANGE = "interferenceColorRange";
	/**
	 * 干扰的连线数.
	 */
	private static final String PARAM_INTERFERENCE_COUNT = "interferenceCount";
	/**
	 * 干扰点的颜色范围. 格式为: nnn,nnn 的字符串.
	 */
	private static final String PARAM_INTERFERENCE_DOT_COLOR_RANGE = "interferenceDotColorRange";
	/**
	 * 干扰的点数.
	 */
	private static final String PARAM_INTERFERENCE_DOT_COUNT = "interferenceDotCount";
	/**
	 * 验证码的颜色范围. 格式为: nnn,nnn 的字符串.
	 */
	private static final String PARAM_CAPTCHA_COLOR_RANGE = "captchaColorRange";
	/**
	 * 验证码的字体范围. 格式为: nnn,nnn 的字符串.
	 */
	private static final String PARAM_CAPTCHA_FONT_RANGE = "captchaFontRange";
	/**
	 * 验证码的字体大小.
	 */
	private static final String PARAM_CAPTCHA_FONT_SIZE = "captchaFontSize";
	/**
	 * 默认的自动验证码
	 */
	private static final String PARAM_AUTO_PASS_VALUE = "autoPassValue";

	// 默认值定义
	private static final String DEFAULT_CAPTCHA_RANGE = "ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
	private static final int DEFAULT_IMAGE_WIDTH = 57;
	private static final int DEFAULT_IMAGE_HEIGHT = 25;
	private static final int DEFAULT_CAPTCHA_COUNT = 4;
	private static final String DEFAULT_SESSION_KEY = "sessionKey";
	private static final String DEFAULT_BACKGROUND_COLOR_RANGE = "220,250";
	private static final String DEFAULT_INTERFERENCE_COLOR_RANGE = "160,210";
	private static final int DEFAULT_INTERFERENCE_COUNT = 50;
	private static final String DEFAULT_INTERFERENCE_DOT_COLOR_RANGE = "130,240";
	private static final int DEFAULT_INTERFERENCE_DOT_COUNT = 100;
	private static final String DEFAULT_CAPTCHA_COLOR_RANGE = "10,150";
	private static final String DEFAULT_CAPTCHA_FONT_RANGE = "Arial,Times New Roman,Tahoma,Helvetica,sans-serif";
	private static final int DEFAULT_CAPTCHA_FONT_SIZE = 16;

	private static String captchaRange = DEFAULT_CAPTCHA_RANGE;
	private static int imageWidth = DEFAULT_IMAGE_WIDTH;
	private static int imageHeight = DEFAULT_IMAGE_HEIGHT;
	private static int captchaCount = DEFAULT_CAPTCHA_COUNT;
	private static String sessionKey = DEFAULT_SESSION_KEY;
	private static String backgroundColorRange = DEFAULT_BACKGROUND_COLOR_RANGE;
	private static String interferenceColorRange = DEFAULT_INTERFERENCE_COLOR_RANGE;
	private static int interferenceCount = DEFAULT_INTERFERENCE_COUNT;
	private static String interferenceDotColorRange = DEFAULT_INTERFERENCE_DOT_COLOR_RANGE;
	private static int interferenceDotCount = DEFAULT_INTERFERENCE_DOT_COUNT;
	private static String captchaColorRange = DEFAULT_CAPTCHA_COLOR_RANGE;
	private static String captchaFontRange = DEFAULT_CAPTCHA_FONT_RANGE;
	private static int captchaFontSize = DEFAULT_CAPTCHA_FONT_SIZE;
	private static String autoPassValue;

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		initParameters(config);
	}

	/**
	 * 初始化web.xml中定义的servlet init-param.
	 */
	private void initParameters(final ServletConfig config) {
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_CAPTCHA_RANGE))) {
			captchaRange = config.getInitParameter(PARAM_CAPTCHA_RANGE);
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_IMAGE_WIDTH))) {
			imageWidth = Integer.valueOf(config.getInitParameter(PARAM_IMAGE_WIDTH));
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_IMAGE_HEIGHT))) {
			imageHeight = Integer.valueOf(config.getInitParameter(PARAM_IMAGE_HEIGHT));
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_CAPTCHA_COUNT))) {
			captchaCount = Integer.valueOf(config.getInitParameter(PARAM_CAPTCHA_COUNT));
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_SESSION_KEY))) {
			sessionKey = config.getInitParameter(PARAM_SESSION_KEY);
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_BACKGROUND_COLOR_RANGE))) {
			backgroundColorRange = config.getInitParameter(PARAM_BACKGROUND_COLOR_RANGE);
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_INTERFERENCE_COLOR_RANGE))) {
			interferenceColorRange = config.getInitParameter(PARAM_INTERFERENCE_COLOR_RANGE);
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_INTERFERENCE_COUNT))) {
			interferenceCount = Integer.valueOf(config.getInitParameter(PARAM_INTERFERENCE_COUNT));
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_INTERFERENCE_DOT_COLOR_RANGE))) {
			interferenceDotColorRange = config.getInitParameter(PARAM_INTERFERENCE_DOT_COLOR_RANGE);
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_INTERFERENCE_DOT_COUNT))) {
			interferenceDotCount = Integer.valueOf(config.getInitParameter(PARAM_INTERFERENCE_DOT_COUNT));
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_CAPTCHA_COLOR_RANGE))) {
			captchaColorRange = config.getInitParameter(PARAM_CAPTCHA_COLOR_RANGE);
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_CAPTCHA_FONT_RANGE))) {
			captchaFontRange = config.getInitParameter(PARAM_CAPTCHA_FONT_RANGE);
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_CAPTCHA_FONT_SIZE))) {
			captchaFontSize = Integer.valueOf(config.getInitParameter(PARAM_CAPTCHA_FONT_SIZE));
		}
		if (StringUtils.isNotBlank(config.getInitParameter(PARAM_AUTO_PASS_VALUE))) {
			autoPassValue = config.getInitParameter(PARAM_AUTO_PASS_VALUE);
		}
	}

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		ServletUtils.setDisableCacheHeader(response);
		response.setContentType("image/jpeg");

		BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
		Graphics2D graphics = image.createGraphics();

		char[] captchaChallenge = getCaptchaChallenge();
		// 增加干扰线
		image = addInterferenceLine(image);
		// 增加干扰点
		image = addInterferenceDot(image);
		
		// 增加验证码
		addCaptchaChallenge(graphics, captchaChallenge);
		

		// 设置背景色
		image = addBackground(image);
		
		HttpSession session = request.getSession();
		session.setAttribute(sessionKey, new String(captchaChallenge));
		ImageIO.write(image, "jpg", response.getOutputStream());
	}


	private void addCaptchaChallenge(Graphics2D graphics, char[] captchaChallenge) {
		Font[] fonts = getFonts();
		int charSpace = 5;

		RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
		graphics.setRenderingHints(hints);

		FontRenderContext frc = graphics.getFontRenderContext();

		Random random = new Random();

		int startPosY = (imageHeight - captchaFontSize) / 5 + captchaFontSize;

		int len = captchaChallenge.length;
		Font[] chosenFonts = new Font[len];
		int[] charWidths = new int[len];
		int widthNeeded = 0;
		for (int i = 0; i < len; i++) {
			chosenFonts[i] = fonts[random.nextInt(fonts.length)];

			char[] charToDraw = new char[] { captchaChallenge[i] };
			GlyphVector gv = chosenFonts[i].createGlyphVector(frc, charToDraw);
			charWidths[i] = (int) gv.getVisualBounds().getWidth();
			if (i > 0) {
				widthNeeded = widthNeeded + 2;
			}
			widthNeeded = widthNeeded + charWidths[i];
		}

		int startPosX = (imageWidth - widthNeeded) / 2 - charSpace;
		for (int i = 0; i < len; i++) {
			graphics.setColor(createRangeColor(captchaColorRange));
			graphics.setFont(chosenFonts[i]);
			char[] charToDraw = new char[] { captchaChallenge[i] };
			graphics.drawChars(charToDraw, 0, charToDraw.length, startPosX, startPosY + random.nextInt(charSpace) / 2);
			startPosX = startPosX + (int) charWidths[i] + charSpace;
		}
	}
	
	private Font[] getFonts() {
		String[] fontNames = captchaFontRange.split(",");
		int len = fontNames.length;
		Font[] fonts = new Font[fontNames.length];
		for (int i = 0; i < len; i++) {
			fonts[i] = new Font(fontNames[i], Font.BOLD, captchaFontSize);
		}
		return fonts;
	}
	
	private BufferedImage addBackground(BufferedImage image) {
		// create an opaque image
		BufferedImage imageWithBackground = new BufferedImage(imageWidth, imageHeight,
				BufferedImage.TYPE_INT_RGB);

		Graphics2D graphics = (Graphics2D) imageWithBackground.getGraphics();

		graphics.setRenderingHints(createNoiseHints());
		GradientPaint paint = new GradientPaint(0, 0, createRangeColor(backgroundColorRange), imageWidth, imageHeight,
				createRangeColor(backgroundColorRange));
		graphics.setPaint(paint);
		graphics.fill(new Rectangle2D.Double(0, 0, imageWidth, imageHeight));
		
		// draw the transparent image over the background
		graphics.drawImage(image, 0, 0, null);
		
		return imageWithBackground;
	}

	private RenderingHints createNoiseHints() {
		RenderingHints hints = new RenderingHints(
				RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_OFF);

		hints.add(new RenderingHints(RenderingHints.KEY_COLOR_RENDERING,
				RenderingHints.VALUE_COLOR_RENDER_QUALITY));
		hints.add(new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION,
				RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY));

		hints.add(new RenderingHints(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY));
		return hints;
	}

	private BufferedImage addInterferenceLine(BufferedImage image) {
		Graphics2D graphics = (Graphics2D) image.getGraphics();
		graphics.setRenderingHints(createNoiseHints());

		Random r = new Random();
		int maxDistance = imageHeight / 2;
		for (int i = 0; i < interferenceCount; i++) {
			graphics.setColor(createRangeColor(interferenceColorRange));
			int x1 = r.nextInt(imageWidth);
		    int y1 = r.nextInt(imageHeight);
		    int x2 = r.nextInt(maxDistance);
		    int y2 = r.nextInt(maxDistance);
		    graphics.drawLine(x1, y1, x1 + x2, y1 + y2);
		}
		return image;
	}

	private BufferedImage addInterferenceDot(BufferedImage image) {
		Graphics2D graphics = (Graphics2D) image.getGraphics();
		graphics.setRenderingHints(createNoiseHints());
		
		Random r = new Random();
		for (int i = 0; i < interferenceDotCount; i++) {
			graphics.setColor(createRangeColor(interferenceDotColorRange));
		    int x = r.nextInt(imageWidth);
		    int y = r.nextInt(imageHeight);
		    graphics.drawOval(x, y, 0, 0);
		}
		return image;
	}

	private char[] getCaptchaChallenge() {
		if (StringUtils.isNotBlank(autoPassValue)) {
			return autoPassValue.toCharArray();
		}
		char[] challenges = new char[captchaCount];
		int len = captchaRange.length();
		Random random = new Random();
		for (int i = 0; i < captchaCount; ++i) {
			int rnd = random.nextInt(len);
			challenges[i] = captchaRange.charAt(rnd);
		}
		return challenges;
	}

	private Color createRangeColor(String colorRange) {
		String[] range = StringUtils.split(colorRange, ',');
		int begin = Integer.valueOf(range[0]);
		int end = Integer.valueOf(range[1]);
		if (begin > 255) {
			begin = 255;
		}
		if (end > 255) {
			end = 255;
		}
		Random r = new Random();
		int red = begin + r.nextInt(end - begin);
		int green = begin + r.nextInt(end - begin);
		int blue = begin + r.nextInt(end - begin);
		return new Color(red, green, blue);
	}

	/**
	 * 是否和session里的验证码一致.
	 * 
	 * @param request
	 * @param captchaSessionKey
	 *            存放在session里的captcha KEY.
	 * @param userCaptchaResponse
	 *            用户输入的验证码
	 * @return 若和session里的验证码一致返回true, 否则返回false.
	 */
	public static boolean validate(HttpServletRequest request, String captchaSessionKey, String userCaptchaResponse) {
		if (request.getSession(false) == null || StringUtils.isBlank(userCaptchaResponse)) {
			return false;
		}
		HttpSession session = request.getSession();
		String captchaSessionValue = (String) session.getAttribute(captchaSessionKey);
		session.removeAttribute(captchaSessionKey);

		return userCaptchaResponse.equalsIgnoreCase(captchaSessionValue);
	}
}
